package jp.seraph.jsade.util;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.math.geometry.Vector3D;

import jp.seraph.jsade.math.Angle;
import jp.seraph.jsade.math.Point3D;
import jp.seraph.jsade.message.MessageUtil;
import jp.seraph.jsade.perceptor.ForceResistancePerceptor;
import jp.seraph.jsade.perceptor.GameState;
import jp.seraph.jsade.perceptor.GameStatePerceptor;
import jp.seraph.jsade.perceptor.GyroPerceptor;
import jp.seraph.jsade.perceptor.HearPerceptor;
import jp.seraph.jsade.perceptor.HingeJointPerceptor;
import jp.seraph.jsade.perceptor.NullPerceptor;
import jp.seraph.jsade.perceptor.Perceptor;
import jp.seraph.jsade.perceptor.SeeObject;
import jp.seraph.jsade.perceptor.SeeObjectType;
import jp.seraph.jsade.perceptor.SeePerceptor;
import jp.seraph.jsade.perceptor.TimePerceptor;
import jp.seraph.jsade.sexpression.Node;

/**
 * 内部的に使用するパーセプタの生成メソッドを含むユーティリティ
 */
public class PerceptorUtil {
    private PerceptorUtil() {

    }

    public static Perceptor createPerceptor(Node aNode) throws IllegalArgumentException {
        if (aNode.childCount() <= 1 || aNode.getChild(0).isNode())
            throw new IllegalArgumentException();

        String tPerceptorName = aNode.getLeftString();
        PerceptorBuilder tBuilder = BuildersContainer.getBuilder(tPerceptorName);
        if (tBuilder == null)
            throw new IllegalArgumentException(MessageUtil.unsupportedPerceptor(tPerceptorName));

        return tBuilder.build(aNode);
    }

    private interface PerceptorBuilder {
        public Perceptor build(Node aNode);
    }

    private static class GameStateBuilder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */
        public Perceptor build(Node aNode) {
            String tStateStr = aNode.getValue("pm");
            String tTimeStr = aNode.getValue("t");

            if (tStateStr == null || tTimeStr == null)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            String tTeam = aNode.getValue("team");
            // String tUniform = aNode.getValue("unum");
            
            GameState tState = null;
            double tTime = 0;
            try{
                tState = GameState.getState(tStateStr);
                tTime = Double.parseDouble(tTimeStr);
            }catch(Exception e){
                e.printStackTrace();
                return new NullPerceptor();
            }

            return new GameStatePerceptor(tState, tTime, tTeam);
        }
    }

    private static class GyroBuilder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */
        public Perceptor build(Node aNode) {
            String tName = aNode.getValue("n");
            String[] tValues = aNode.getValues("rt");
            if (tName == null || tValues.length != 3)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            double tX = Double.parseDouble(tValues[0]);
            double tY = Double.parseDouble(tValues[1]);
            double tZ = Double.parseDouble(tValues[2]);
            return new GyroPerceptor("tName", new Vector3D(tX, tY, tZ));
        }
    }

    private static class HingeJointBuilder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */
        public Perceptor build(Node aNode) {
            String tName = aNode.getValue("n");
            String tAngleStr = aNode.getValue("ax");

            if (tName == null || tAngleStr == null)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            return new HingeJointPerceptor(tName, Double.parseDouble(tAngleStr));
        }
    }

    private static class SeeBuilder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */
        public Perceptor build(Node aNode) {
            int tCount = aNode.childCount() - 1;
            SeeObject[] tObjects = new SeeObject[tCount];

            for (int i = 0; i < tCount; i++) {
                try {
                    tObjects[i] = this.buildSeeObject(aNode.getChildAsNode(i + 1));
                } catch (Exception e) {
                    System.out.println("SeeObjectのParseに失敗しました");
                    e.printStackTrace();
                    tObjects[i] = null;
                }
            }

            return new SeePerceptor(tObjects);
        }

        private SeeObject buildSeeObject(Node aNode) {
            if(aNode.childCount() == 2 && aNode.getLeftString().equals("id")){
                System.out.println("See Node has Only 2 Nodes. I insert \"P\" at 0");
                aNode.insertChild("P", 0);
            }
            
            SeeObjectType tType = SeeObjectType.getObjectType(aNode.getLeftString());

            if (tType == SeeObjectType.PLAYER)
                return buildPlayerSeeObject(aNode);
            else
                return buildSeeObject(aNode, tType);
        }

        private SeeObject buildPlayerSeeObject(Node aNode) {
            String[] tPolValues = aNode.getValues("pol");
            String tTeam = aNode.getValue("team");
            String tIdStr = aNode.getValue("id");

            if (tPolValues == null || tTeam == null || tIdStr == null || tPolValues.length != 3)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            double tDistance = Double.parseDouble(tPolValues[0]);
            double tHorizontalAngle = Double.parseDouble(tPolValues[1]);
            double tLatitudalAngle = Double.parseDouble(tPolValues[2]);
            int tID = Integer.parseInt(tIdStr);

            return new SeeObject(SeeObjectType.PLAYER, tTeam, tID, tDistance, tHorizontalAngle, tLatitudalAngle);
        }

        private SeeObject buildSeeObject(Node aNode, SeeObjectType aType) {
            String[] tValues = aNode.getValues("pol");

            if (tValues.length != 3)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            double tDistance = Double.parseDouble(tValues[0]);
            double tHorizontalAngle = Double.parseDouble(tValues[1]);
            double tLatitudalAngle = Double.parseDouble(tValues[2]);

            return new SeeObject(aType, tDistance, tHorizontalAngle, tLatitudalAngle);
        }
    }

    private static class TimeBuilder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */
        public Perceptor build(Node aNode) {
            String tNowStr = aNode.getValue("now");

            if (tNowStr == null)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            return new TimePerceptor(Double.parseDouble(tNowStr));
        }
    }

    private static class ForceResistanceBulder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */

        public Perceptor build(Node aNode) {
            String tName = aNode.getValue("n");
            String[] tCentorValues = aNode.getValues("c");
            String[] tForceValues = aNode.getValues("f");

            if (tName == null || tCentorValues.length != 3 || tForceValues.length != 3)
                throw new IllegalArgumentException("ノードの構造が想定のものと違います。");

            Double tPointX = Double.parseDouble(tCentorValues[0]);
            Double tPointY = Double.parseDouble(tCentorValues[1]);
            Double tPointZ = Double.parseDouble(tCentorValues[2]);
            Double tForceX = Double.parseDouble(tForceValues[0]);
            Double tForceY = Double.parseDouble(tForceValues[1]);
            Double tForceZ = Double.parseDouble(tForceValues[2]);

            return new ForceResistancePerceptor(tName, new Point3D(tPointX, tPointY, tPointZ), new Vector3D(tForceX, tForceY, tForceZ));
        }
    }

    private static class HearBulder implements PerceptorBuilder {

        /**
         * 
         * @see jp.seraph.jsade.util.PerceptorUtil.PerceptorBuilder#build(jp.seraph.jsade.sexpression.Node)
         */

        public Perceptor build(Node aNode) {
            double tTime;
            String tStrAngle;
            String tMessage;

            tTime = aNode.getChildAsLeaf(1).getValueAsDouble();
            tStrAngle = aNode.getChildAsLeaf(2).getValue();
            tMessage = aNode.getChildAsLeaf(3).getValue();

            Angle tAngle = "self".equals(tStrAngle) ? null : Angle.createAngleAsDegree(Double.parseDouble(tStrAngle));

            return new HearPerceptor(tTime, tAngle, tMessage);
        }
    }

    private static class BuildersContainer {
        private BuildersContainer() {
        }

        private static Map<String, PerceptorBuilder> BUILDER_MAP = new HashMap<String, PerceptorBuilder>();
        static {
            BUILDER_MAP.put("GS", new GameStateBuilder());
            BUILDER_MAP.put("GYR", new GyroBuilder());
            BUILDER_MAP.put("HJ", new HingeJointBuilder());
            BUILDER_MAP.put("See", new SeeBuilder());
            BUILDER_MAP.put("time", new TimeBuilder());
            BUILDER_MAP.put("FRP", new ForceResistanceBulder());
            BUILDER_MAP.put("hear", new HearBulder());
        }

        public static PerceptorBuilder getBuilder(String aPerceptorName) {
            return BUILDER_MAP.get(aPerceptorName);
        }
    }
}
